// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "net/cert/caching_cert_verifier.h"

#include <memory>

#include "base/files/file_path.h"
#include "base/memory/ptr_util.h"
#include "base/memory/ref_counted.h"
#include "net/base/net_errors.h"
#include "net/base/test_completion_callback.h"
#include "net/cert/cert_verifier.h"
#include "net/cert/cert_verify_result.h"
#include "net/cert/mock_cert_verifier.h"
#include "net/cert/x509_certificate.h"
#include "net/log/net_log.h"
#include "net/test/cert_test_util.h"
#include "net/test/test_data_directory.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using testing::_;
using testing::Mock;
using testing::Return;
using testing::ReturnRef;

namespace net {

namespace {

    class MockCacheVisitor : public CachingCertVerifier::CacheVisitor {
    public:
        MockCacheVisitor() { }
        ~MockCacheVisitor() override { }

        MOCK_METHOD5(VisitEntry,
            bool(const CachingCertVerifier::RequestParams& params,
                int error,
                const CertVerifyResult& result,
                base::Time verification_time,
                base::Time expiration_time));
    };

} // namespace

class CachingCertVerifierTest : public ::testing::Test {
public:
    CachingCertVerifierTest()
        : verifier_(base::MakeUnique<MockCertVerifier>())
    {
    }
    ~CachingCertVerifierTest() override { }

protected:
    CachingCertVerifier verifier_;
};

TEST_F(CachingCertVerifierTest, CacheHit)
{
    base::FilePath certs_dir = GetTestCertsDirectory();
    scoped_refptr<X509Certificate> test_cert(
        ImportCertFromFile(certs_dir, "ok_cert.pem"));
    ASSERT_TRUE(test_cert.get());

    int error;
    CertVerifyResult verify_result;
    TestCompletionCallback callback;
    std::unique_ptr<CertVerifier::Request> request;

    error = callback.GetResult(verifier_.Verify(
        CertVerifier::RequestParams(test_cert, "www.example.com", 0,
            std::string(), CertificateList()),
        nullptr, &verify_result, callback.callback(), &request, BoundNetLog()));
    ASSERT_TRUE(IsCertificateError(error));
    ASSERT_EQ(1u, verifier_.requests());
    ASSERT_EQ(0u, verifier_.cache_hits());
    ASSERT_EQ(1u, verifier_.GetCacheSize());

    error = verifier_.Verify(
        CertVerifier::RequestParams(test_cert, "www.example.com", 0,
            std::string(), CertificateList()),
        nullptr, &verify_result, callback.callback(), &request, BoundNetLog());
    // Synchronous completion.
    ASSERT_NE(ERR_IO_PENDING, error);
    ASSERT_TRUE(IsCertificateError(error));
    ASSERT_FALSE(request);
    ASSERT_EQ(2u, verifier_.requests());
    ASSERT_EQ(1u, verifier_.cache_hits());
    ASSERT_EQ(1u, verifier_.GetCacheSize());
}

TEST_F(CachingCertVerifierTest, Visitor)
{
    base::FilePath certs_dir = GetTestCertsDirectory();
    scoped_refptr<X509Certificate> test_cert(
        ImportCertFromFile(certs_dir, "ok_cert.pem"));
    ASSERT_TRUE(test_cert.get());

    TestCompletionCallback callback;
    std::unique_ptr<CertVerifier::Request> request;

    // Add some entries to the cache
    CertVerifier::RequestParams params1(test_cert, "www.example.com", 0,
        std::string(), CertificateList());
    CertVerifyResult result1;
    int error1 = callback.GetResult(verifier_.Verify(params1, nullptr, &result1,
        callback.callback(),
        &request, BoundNetLog()));
    ASSERT_TRUE(IsCertificateError(error1));
    ASSERT_EQ(1u, verifier_.requests());
    ASSERT_EQ(0u, verifier_.cache_hits());
    ASSERT_EQ(1u, verifier_.GetCacheSize());

    CertVerifier::RequestParams params2(test_cert, "www.example.net", 0,
        std::string(), CertificateList());
    CertVerifyResult result2;
    int error2 = callback.GetResult(verifier_.Verify(params2, nullptr, &result2,
        callback.callback(),
        &request, BoundNetLog()));
    ASSERT_TRUE(IsCertificateError(error2));
    ASSERT_EQ(2u, verifier_.requests());
    ASSERT_EQ(0u, verifier_.cache_hits());
    ASSERT_EQ(2u, verifier_.GetCacheSize());

    CertVerifier::RequestParams params3(test_cert, "www.example.org", 0,
        std::string(), CertificateList());
    CertVerifyResult result3;
    int error3 = callback.GetResult(verifier_.Verify(params3, nullptr, &result3,
        callback.callback(),
        &request, BoundNetLog()));
    ASSERT_TRUE(IsCertificateError(error3));
    ASSERT_EQ(3u, verifier_.requests());
    ASSERT_EQ(0u, verifier_.cache_hits());
    ASSERT_EQ(3u, verifier_.GetCacheSize());

    // Iterate through all entries.
    {
        MockCacheVisitor mock_visitor;
        EXPECT_CALL(mock_visitor, VisitEntry(params1, error1, _, _, _))
            .WillOnce(Return(true));
        EXPECT_CALL(mock_visitor, VisitEntry(params2, error2, _, _, _))
            .WillOnce(Return(true));
        EXPECT_CALL(mock_visitor, VisitEntry(params3, error3, _, _, _))
            .WillOnce(Return(true));
        verifier_.VisitEntries(&mock_visitor);
    }

    // Now perform partial iteration
    {
        MockCacheVisitor mock_visitor;
        ::testing::InSequence sequence;
        EXPECT_CALL(mock_visitor, VisitEntry(_, _, _, _, _)).WillOnce(Return(true));
        EXPECT_CALL(mock_visitor, VisitEntry(_, _, _, _, _))
            .WillOnce(Return(false));
        verifier_.VisitEntries(&mock_visitor);
    }
}

TEST_F(CachingCertVerifierTest, AddsEntries)
{
    base::FilePath certs_dir = GetTestCertsDirectory();
    scoped_refptr<X509Certificate> test_cert(
        ImportCertFromFile(certs_dir, "ok_cert.pem"));
    ASSERT_TRUE(test_cert.get());

    CertVerifyResult result_1;
    result_1.verified_cert = test_cert;
    result_1.cert_status = CERT_STATUS_WEAK_SIGNATURE_ALGORITHM;
    result_1.has_md2 = true;
    result_1.is_issued_by_known_root = false;

    CertVerifyResult result_2;
    result_2.verified_cert = test_cert;
    result_2.cert_status = CERT_STATUS_IS_EV;
    result_2.is_issued_by_known_root = true;

    CertVerifier::RequestParams params(test_cert, "www.example.com", 0,
        std::string(), CertificateList());

    base::Time now = base::Time::Now();

    // On an empty cache, it should be fine to add an entry.
    EXPECT_TRUE(verifier_.AddEntry(params, ERR_CERT_WEAK_KEY, result_1, now));
    ASSERT_EQ(0u, verifier_.requests());
    ASSERT_EQ(0u, verifier_.cache_hits());
    ASSERT_EQ(1u, verifier_.GetCacheSize());

    TestCompletionCallback callback;
    std::unique_ptr<CertVerifier::Request> request;

    CertVerifyResult cached_result;
    int error = callback.GetResult(
        verifier_.Verify(params, nullptr, &cached_result, callback.callback(),
            &request, BoundNetLog()));
    ASSERT_EQ(ERR_CERT_WEAK_KEY, error);
    EXPECT_TRUE(cached_result.has_md2);
    EXPECT_FALSE(cached_result.is_issued_by_known_root);

    ASSERT_EQ(1u, verifier_.requests());
    ASSERT_EQ(1u, verifier_.cache_hits());
    ASSERT_EQ(1u, verifier_.GetCacheSize());

    // But it should not be fine to replace it with an existing entry, even
    // if that entry is 'newer'.
    EXPECT_FALSE(verifier_.AddEntry(params, OK, result_2,
        now + base::TimeDelta::FromMinutes(1)));

    error = callback.GetResult(verifier_.Verify(params, nullptr, &cached_result,
        callback.callback(), &request,
        BoundNetLog()));
    ASSERT_EQ(ERR_CERT_WEAK_KEY, error);
    EXPECT_TRUE(cached_result.has_md2);
    EXPECT_FALSE(cached_result.is_issued_by_known_root);

    ASSERT_EQ(2u, verifier_.requests());
    ASSERT_EQ(2u, verifier_.cache_hits());
    ASSERT_EQ(1u, verifier_.GetCacheSize());
}

// Tests the same server certificate with different intermediate CA
// certificates.  These should be treated as different certificate chains even
// though the two X509Certificate objects contain the same server certificate.
TEST_F(CachingCertVerifierTest, DifferentCACerts)
{
    base::FilePath certs_dir = GetTestCertsDirectory();

    scoped_refptr<X509Certificate> server_cert = ImportCertFromFile(certs_dir, "salesforce_com_test.pem");
    ASSERT_TRUE(server_cert);

    scoped_refptr<X509Certificate> intermediate_cert1 = ImportCertFromFile(certs_dir, "verisign_intermediate_ca_2011.pem");
    ASSERT_TRUE(intermediate_cert1);

    scoped_refptr<X509Certificate> intermediate_cert2 = ImportCertFromFile(certs_dir, "verisign_intermediate_ca_2016.pem");
    ASSERT_TRUE(intermediate_cert2);

    X509Certificate::OSCertHandles intermediates;
    intermediates.push_back(intermediate_cert1->os_cert_handle());
    scoped_refptr<X509Certificate> cert_chain1 = X509Certificate::CreateFromHandle(server_cert->os_cert_handle(),
        intermediates);
    ASSERT_TRUE(cert_chain1);

    intermediates.clear();
    intermediates.push_back(intermediate_cert2->os_cert_handle());
    scoped_refptr<X509Certificate> cert_chain2 = X509Certificate::CreateFromHandle(server_cert->os_cert_handle(),
        intermediates);
    ASSERT_TRUE(cert_chain2);

    int error;
    CertVerifyResult verify_result;
    TestCompletionCallback callback;
    std::unique_ptr<CertVerifier::Request> request;

    error = callback.GetResult(verifier_.Verify(
        CertVerifier::RequestParams(cert_chain1, "www.example.com", 0,
            std::string(), CertificateList()),
        nullptr, &verify_result, callback.callback(), &request, BoundNetLog()));
    ASSERT_TRUE(IsCertificateError(error));
    ASSERT_EQ(1u, verifier_.requests());
    ASSERT_EQ(0u, verifier_.cache_hits());
    ASSERT_EQ(1u, verifier_.GetCacheSize());

    error = callback.GetResult(verifier_.Verify(
        CertVerifier::RequestParams(cert_chain2, "www.example.com", 0,
            std::string(), CertificateList()),
        nullptr, &verify_result, callback.callback(), &request, BoundNetLog()));
    ASSERT_TRUE(IsCertificateError(error));
    ASSERT_EQ(2u, verifier_.requests());
    ASSERT_EQ(0u, verifier_.cache_hits());
    ASSERT_EQ(2u, verifier_.GetCacheSize());
}

} // namespace net
